/*

  xmunipack - tune panel

  Copyright © 2009-2011 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "xmunipack.h"
//#include "tune.h"
#include <wx/wx.h>
#include <wx/event.h>
#include <wx/dcbuffer.h>
#include <wx/dcmemory.h>
#include <wx/arrstr.h>
#include <wx/statline.h>
#include <wx/graphics.h>
#include <cfloat>
#include <vector>

using namespace std;



// BEGIN_EVENT_TABLE(MuniGraph, wxPanel)
//   EVT_PAINT(MuniGraph::OnPaint)
//   EVT_SIZE(MuniGraph::OnSize)
// END_EVENT_TABLE()


// -- MuniMiniDisplay

class MuniMiniDisplay: public wxWindow
{
private:

  int width, height;
  wxBitmap bitmap;

  void OnPaint(wxPaintEvent& event)
  {
    if( ! bitmap.IsOk() ) return;

    wxAutoBufferedPaintDC dc(this);

    // draw image bitmap
    dc.DrawBitmap(bitmap,0,0,false);
    
    // draw decorations
    dc.SetBrush(*wxTRANSPARENT_BRUSH);
    dc.SetPen(*wxBLACK_PEN);
    dc.DrawRectangle(0,0,width,height);
    dc.SetPen(*wxWHITE_PEN);
    dc.DrawRectangle(1,1,width-2,height-2);
  }


public:

  MuniMiniDisplay(wxWindow *win, int w, int h): 
    wxWindow(win,wxID_ANY), width(w), height(h)
  {
    SetBackgroundStyle(wxBG_STYLE_CUSTOM);
    Bind(wxEVT_PAINT,&MuniMiniDisplay::OnPaint,this);
  }

  wxSize DoGetBestSize() const
  {
    /*
    const int t = 300;
    return wxSize(width > t ? width : t, height > t ? height : t);
    */
    return wxSize(width,height);
  }

  void SetImage(const wxImage& i) 
  { 
    bitmap = wxBitmap(i);
    width = i.GetWidth();
    height = i.GetHeight();
    Refresh();
  }
};


// ---- xTrafo

// class xTrafo
// {
// public:
//   xTrafo(double,double,double,double);
  
//   void SetAxes(double,double,double,double);
//   void SetArea(int,int,int,int);

//   void Linear(double,double,double *,double *);
//   void Scale(double,double,double *,double *);

// private:
  
//   double xmin,xmax,ymin,ymax;
//   double imin, imax, jmin, jmax;
//   double dx,dy;
// };


// xTrafo::xTrafo(double x0,double y0,double x1,double y1):
//   xmin(x0), xmax(x1), ymin(y0), ymax(y1), 
//   imin(0.0), imax(-1.0), jmin(0.0), jmax(-1.0), dx(1.0), dy(1.0)
// {
//   SetAxes(x0,y0,x1,y1);
// }


// void xTrafo::SetAxes(double x0,double y0,double x1,double y1)
// {
//   xmin = x0;
//   xmax = x1;
//   ymin = y0;
//   ymax = y1;

//   if( fabs(xmin) < DBL_EPSILON && fabs(xmax) < DBL_EPSILON ) {
//     xmin = 0.0;
//     xmax = 1.0;
//   }
//   if( fabs(ymin - ymax) < DBL_EPSILON ) {
//     ymin = -1.0;
//     ymax = 1.0;
//   }

//   dx = (xmax - xmin)/double(imax - imin);
//   dy = (ymax - ymin)/double(jmax - jmin);
// }

// void xTrafo::SetArea(int i, int j, int w, int h)
// {
//   imin = i;
//   imax = i + w;
//   jmin = j;
//   jmax = j + h;

//   dx = (xmax - xmin)/double(imax - imin);
//   dy = (ymax - ymin)/double(jmax - jmin);
// }


// void xTrafo::Linear(double x, double y, double *i, double *j)
// {
//   *i = imin + (x - xmin)/dx;
//   *j = jmin + (ymax - y)/dy;
// }

// void xTrafo::Scale(double w, double h, double *i, double *j)
// {
//   *i = w/dx;
//   *j = h/dy;
// }



// // ---- MuniGraph ---------------------------------------------

// MuniGraph::MuniGraph(wxWindow *w): wxPanel(w,wxID_ANY,wxDefaultPosition,
// 					   wxSize(300,185)), itt(0),
// 				   strip_width(12), big_tic(5),small_tic(3)
// {
//   SetBackgroundStyle(wxBG_STYLE_CUSTOM);
//   sf = wxFont(*wxSMALL_FONT);
// }

// MuniGraph::~MuniGraph() { hlist.clear(); }

// wxSize MuniGraph::DoGetBestSize() const
// {
//   return wxSize(300,185);
// }

// void MuniGraph::SetItt(const FitsItt& i) 
// { 
//   itt = i;
//   Refresh();
// }

// void MuniGraph::SetHisto(const FitsHisto& hl)
// {
//   hlist.clear();
//   hlist.push_back(hl);
//   Refresh();
// }

// void MuniGraph::SetHisto(const vector<FitsHisto>& hl)
// {
//   hlist = hl;
//   Refresh();
// }

// void MuniGraph::OnPaint(wxPaintEvent& event)
// {
//   Create();
// }

// void MuniGraph::OnSize(wxSizeEvent& event)
// {
//   Create();
//   event.Skip();
// }


// void MuniGraph::Create()
// {

//   wxSize size = GetClientSize();
//   int width = size.GetWidth();
//   int height = size.GetHeight() - sf.GetPointSize() - 2 - big_tic;

//   // search for maximum in histogram
//   /*
//   int hmax = 0;
//   for(std::vector<FitsHisto>::iterator k = hlist.begin();k != hlist.end();++k){
//     FitsHisto hist = *k;  
//     for(int i = 0; i < hist.NBins(); i++ ) {
//       if( hist.Hist(i) > hmax )
// 	hmax = hist.Hist(i);
//     }
//   }
//   */

//   // estimate y-scale from histogram
//   int hmax = 0;
//   for(std::vector<FitsHisto>::iterator k = hlist.begin();k != hlist.end();++k){
//     FitsHisto hist = *k;  
//     for(int i = 0; i < hist.NBins(); i++ ) {
//       int x = 2.0*hist.Hist(i);
//       if( hist.Cents(i) - itt.GetMed() > itt.GetMad() && x > hmax ) {
// 	hmax = x;
// 	continue;
//       }
//     }
//   }
  

//   wxImage i(size.GetWidth(),size.GetHeight(),false);
//   if( ! i.IsOk() ) return;
//   //  wxLogDebug(_("%d %d"),size.GetWidth(),size.GetHeight());
//   //  wxASSERT(i.IsOk());
//   wxBitmap paper(i);
  
//   wxMemoryDC dc(paper);
//   wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
//   if( gc ) {

//     double xmin = itt.GetRangeMin();
//     double xmax = itt.GetRangeMax();

//     xTrafo trafo(xmin,0.0,xmax,double(hmax));
//     trafo.SetArea(0,0,size.GetWidth(),height);

//     // clear
//     gc->SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
//     wxGraphicsPath gp(gc->CreatePath());
//     gp.AddRectangle(0,0,size.GetWidth(),size.GetHeight());
//     gc->FillPath(gp);

//     // histogram
//     const unsigned char opacity = hlist.size() > 1 ? 128 : 255;
//     wxColour colour(128,128,128,opacity);
//     int pit = 0;
//     for(std::vector<FitsHisto>::iterator k = hlist.begin();k!=hlist.end();++k){
//       FitsHisto hist = *k;  

//       if( ! hist.IsOk() ) continue;

//       if( hlist.size() > 1 ) {
// 	switch (pit) {
// 	case 0: colour.Set(255,0,0,opacity); break;
// 	case 1: colour.Set(0,255,0,opacity); break;
// 	case 2: colour.Set(0,0,255,opacity); break;
// 	default: colour.Set(64,64,64,opacity); break;
// 	}
//       }
//       gc->SetBrush(wxBrush(colour));

//       for(int l = 0; l < hist.NBins(); l++ ) {
// 	double bw = hist.BinWidth();
// 	double bh = hist.Hist(l);
// 	double x,y,w,h;

// 	trafo.Linear(hist.Cents(l)-bw/2.0,bh,&x,&y);
// 	trafo.Scale(bw,bh,&w,&h);
// 	gc->DrawRectangle(x,y,w,h);
//       }

//       pit++;
//       if( pit == 3 ) pit = 0;
//     }

//     gc->SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));

//     double ybase = height;

//     wxPoint2DDouble lines[] = { wxPoint2DDouble(0.0,ybase), 
// 				wxPoint2DDouble(size.GetWidth(),ybase) };
//     gc->DrawLines(2,lines);

//     // tics 
//     double tic = trunc((xmax - xmin)/13.0);
//     tic = (xmax - xmin)/13.0;

//     // rounding to only one place of 1,2,5
//     double p = log10(tic);
//     double e = trunc(p); if( p < 0.0 ) e = e - 1.0; 
//     double e10 = pow(10.0,e);
//     tic = trunc(pow(10.0,p-e));//*e10;
//     float xtics[] = {1.0, 2.0, 5.0, 10.0 };
//     for(int i = 1; i < 4; i++ )
//       if( xtics[i-1] <= tic && tic < xtics[i] ) {
// 	tic = xtics[i];
// 	break;
//       }
//     tic = tic*e10;
    
//     // start on rounded position
//     double xs = trunc(xmin/tic)*tic;
    
//     // tics
//     double x = xs;
//     while( x < xmax ) {
//       double ytic = fabs(fmod(x -(xs+tic),5.0*tic)) < 0.5 ? big_tic : small_tic;
//       double i,j;
//       trafo.Linear(x,0.0,&i,&j);

//       wxPoint2DDouble lines[] = { wxPoint2DDouble(i,j), 
// 				  wxPoint2DDouble(i,j+ytic) };
//       gc->DrawLines(2,lines);

//       x = x + tic;
//     }

//     // labels
//     gc->SetFont(*wxSMALL_FONT,
// 		wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));

//     x = xs + tic;
//     while( x < xmax ) {
//       wxString a;
//       double tw,th,u,v,xoff;
//       a.Printf(wxT("%g"),x);
//       gc->GetTextExtent(a,&tw,&th,&u,&v);
//       xoff = tw/2.0;

//       double i,j;
//       trafo.Linear(x,0.0,&i,&j);
//       gc->DrawText(a,i-xoff,j+big_tic);
//       x = x + 2.0*tic;
//     }

//     // plot chain
//     const int npoints = width / 10.0;
//     gc->SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));

//     trafo.SetAxes(xmin,-15.0,xmax,270.0);
    
//     double step = (xmax - xmin)/npoints;
//     for(int i = 1; i < npoints; i++) {
//       double f = xmin + i*step;
//       unsigned char c = itt.Fscale(f);
//       gc->SetBrush(wxColour(c,c,c,128));
//       double x,y;
//       trafo.Linear(f,double(c),&x,&y);
//       gc->DrawEllipse(x-3.0,y-3.0,6.0,6.0);
//     }
//     delete gc;
//   }

//   // draw
//   wxAutoBufferedPaintDC canvas(this);
//   canvas.Blit(0,0,size.GetWidth(),size.GetHeight(), &dc, 0, 0);
// }



// ---- MuniLUTus  ---------------------------------------------

MuniLUTus::MuniLUTus(wxWindow *w): wxPanel(w)
{
  SetBackgroundStyle(wxBG_STYLE_CUSTOM);
  Bind(wxEVT_PAINT,&MuniLUTus::OnPaint,this);
  Bind(wxEVT_SIZE,&MuniLUTus::OnSize,this);
}

wxSize MuniLUTus::DoGetBestSize() const
{
  //  return wxSize(225,300);
  return wxSize(10,10);
}

void MuniLUTus::SetPalette(const FitsPalette& p)
{ 
  pal = p;
  Refresh();
}

void MuniLUTus::OnPaint(wxPaintEvent& event)
{
  Create();
}

void MuniLUTus::OnSize(wxSizeEvent& event)
{
  Create();
  event.Skip();
}

void MuniLUTus::Create()
{
  wxSize size = GetSize();
  wxAutoBufferedPaintDC dc(this);

  int n = pal.GetColors();

  const int xtic = 32;
  const int ytic = n/32;
  
  int dx = max(size.GetWidth()/xtic,1);
  int dy = max(size.GetHeight()/ytic,1);
  int xoff = (size.GetWidth() - xtic*dx)/2;
  int yoff = (size.GetHeight() - ytic*dy)/2;  

  n--;
  for(int j = 0; j < ytic; j++)
    for(int i = 0; i < xtic; i++) {
      dc.SetBrush(wxColor(pal.R(n),pal.G(n),pal.B(n)));
      dc.DrawRectangle(xoff+i*dx,yoff+j*dy,dx,dy);
      n--;
    }
}


// ---- MuniTune

MuniTune::MuniTune(wxWindow *w, wxWindowID id, const wxPoint& pos, const wxSize& size, MuniConfig *c,
		   const FitsArray& a, const FitsItt& i, const FitsPalette& p):
  wxDialog(w,id,"Adjustments",pos,size),config(c),array(a),itt(i),pal(p),etune(0)
{

  wxNotebook *book = new wxNotebook(this,wxID_ANY);

  // ITT
  book->AddPage(CreateIttTab(book,itt),"Tone");

  // Palette
  wxNotebookPage *ppanel = new wxPanel(book);

  wxBoxSizer *lsizer = new wxBoxSizer(wxVERTICAL);

  lutus = new MuniLUTus(ppanel);
  lutus->SetPalette(pal);
  lsizer->Add(lutus,wxSizerFlags(1).Expand());

  wxBoxSizer *cchoice = new wxBoxSizer(wxHORIZONTAL);

  lutch = new wxChoice(ppanel,ID_CHOICE_PAL,wxDefaultPosition,
		       wxDefaultSize,FitsPalette::Type_str());
  lutch->SetStringSelection(pal.GetPalette_str());
  cchoice->Add(lutch,wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER).Border(wxRIGHT));

  negcheck = new wxCheckBox(ppanel,ID_CHECK_NEG,"Negative");
  negcheck->SetValue(pal.GetNegative());
  cchoice->Add(negcheck,wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER).Border(wxLEFT));
  lsizer->Add(cchoice,wxSizerFlags().Border().Center());
  ppanel->SetSizer(lsizer);

  book->AddPage(ppanel,"Palette");

  // preview
  mini = new MuniMiniDisplay(this,2*config->icon_size,2*config->icon_size);

  // reset
  wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
  topsizer->Add(mini,wxSizerFlags().Border().Expand());
  topsizer->Add(book,wxSizerFlags().Border().Expand());

  topsizer->Add(new wxButton(this, ID_RESET, "Reset All"),wxSizerFlags().Center().Border());

  SetSizer(topsizer);

  Bind(wxEVT_CLOSE_WINDOW,&MuniTune::OnClose,this);
  Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniTune::OnChoicePal,this,ID_CHOICE_PAL);
  Bind(wxEVT_COMMAND_CHECKBOX_CLICKED,&MuniTune::OnCheckNeg,this,ID_CHECK_NEG);
  Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniTune::OnChoiceItt,this,ID_CHOICE_ITT);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniTune::OnReset,this,ID_RESET);
  Bind(wxEVT_UPDATE_UI,&MuniTune::OnUpdateIttpar,this,ID_ITT_AMP);
  Bind(wxEVT_UPDATE_UI,&MuniTune::OnUpdateIttpar,this,ID_ITT_ZERO);
  Bind(EVT_TUNE,&MuniTune::OnTuneFine,this);
  Bind(wxEVT_IDLE,&MuniTune::OnIdle,this);
  Bind(wxEVT_SCROLL_THUMBTRACK,&MuniTune::OnScroll,this);

  FitsImage fi(a);
  fimage = fi.Thumb(2*config->icon_size,2*config->icon_size);
  Render();

  Layout();
  GetSizer()->SetSizeHints(this);

  // save initial settings (used during reseting)
  itt_original = itt;
  pal_original = pal;

}

MuniTune::MuniTune(wxWindow *w, wxWindowID id, const wxPoint& pos, const wxSize& size, MuniConfig *c,
		   const FitsArray& a, const FitsItt& i, const FitsColor& cc):
  wxDialog(w,id,"Adjustments",pos,size),config(c),array(a),itt(i),color(cc),lutus(0),etune(0)
{
  wxSizerFlags vsizer;
  vsizer.Align(wxALIGN_CENTER_VERTICAL).Border(wxLEFT|wxRIGHT).Expand();

  wxNotebook *book = new wxNotebook(this,wxID_ANY);

  // ITT
  book->AddPage(CreateIttTab(book,itt),"Tone");

  // Color
  wxNotebookPage *cpanel = new wxPanel(book);

  wxBoxSizer *csizer = new wxBoxSizer(wxVERTICAL);

  csizer->Add(new wxStaticText(cpanel, wxID_ANY,"Saturation and Hue"),wxSizerFlags().Center().Border());

  adjsatur = new MuniTuneAdjuster(cpanel,ID_COLOR_SATUR,color.GetSaturation(),0.0,3.0,
				  0.1,1);
  adjsatur->SetToolTip("Color saturation sets amount of colors in image.");
  csizer->Add(adjsatur,vsizer);

  adjhuee = new MuniTuneAdjuster(cpanel,ID_COLOR_HUE,color.GetHue(),-180.0,180.0,
				 10.0);
  adjhuee->SetToolTip("Hue rotates the color space.");
  csizer->Add(adjhuee,vsizer);

  csizer->Add(new wxStaticLine(cpanel,wxID_ANY),wxSizerFlags().Expand().DoubleBorder());
  csizer->Add(new wxStaticText(cpanel, wxID_ANY,"White Point"),wxSizerFlags().Center().Border(wxBOTTOM));

  adjwhiteu = new MuniTuneAdjuster(cpanel,ID_COLOR_WHITEU,color.GetWhitePoint_U(),0.0,1.0,
				   0.01,3,"u:");
  adjwhiteu->SetToolTip("Horizontal (u) Component of white point.");
  csizer->Add(adjwhiteu,vsizer);

  adjwhitev = new MuniTuneAdjuster(cpanel,ID_COLOR_WHITEV,color.GetWhitePoint_V(),0.0,1.0,
				   0.01,3,"v:");
  adjwhitev->SetToolTip("Vertical (v) Component of white point.");
  csizer->Add(adjwhitev,vsizer);

  cpanel->SetSizer(csizer);

  book->AddPage(cpanel,"Colors");

  // Night
  wxNotebookPage *npanel = new wxPanel(book);

  wxBoxSizer *nsizer = new wxBoxSizer(wxVERTICAL);
  ncheck = new wxCheckBox(npanel,ID_CHECK_NIGHT,"Night vision");
  nsizer->Add(ncheck,wxSizerFlags().Center().Border());
  adjnthresh = new MuniTuneAdjuster(npanel,ID_COLOR_NTHRESH,color.GetNightThresh(),0.0,1000.0,
				    1.0);
  adjnthresh->SetToolTip("Threshold is intensity where night vision (bad ligthting conditions) grades to day vision (good lighthing).");
  nsizer->Add(adjnthresh,vsizer);
  adjmeso = new MuniTuneAdjuster(npanel,ID_COLOR_NWIDTH,color.GetNightWidth(),0.0,1000.0,
				 1.0);
  adjmeso->SetToolTip("Mesotopic layer is thickness of logistic curve connecting night and day vision.");
  nsizer->Add(adjmeso,vsizer);

  npanel->SetSizer(nsizer);
  book->AddPage(npanel,"Nite");

  // channels
  if( color.GetColorspace().Find("XYZ") == wxNOT_FOUND ) {

    wxNotebookPage *chpanel = new wxPanel(book);
    wxBoxSizer *chsizer = new wxBoxSizer(wxVERTICAL);
    
    chlist = new wxListView(chpanel,wxID_ANY,wxDefaultPosition,
			    wxDefaultSize,wxLC_REPORT|wxLC_NO_HEADER|wxLC_SINGLE_SEL);
    chlist->SetToolTip("Select channel to set parameters.");
    chsizer->Add(chlist,wxSizerFlags(1).Expand().DoubleBorder(wxLEFT|wxRIGHT));
    
    adjmean = new MuniTuneAdjuster(chpanel,ID_COLOR_MEAN,0.0,-1e5,1e5,1000.0);
    adjmean->SetToolTip("Set mean level.");
    chsizer->Add(adjmean,vsizer);
    
    adjweight = new MuniTuneAdjuster(chpanel,ID_COLOR_WEIGHT,1.0,0.0,2.0,0.1);
    adjweight->SetToolTip("Set image multiplier.");
    chsizer->Add(adjweight,vsizer);
    
    chpanel->SetSizer(chsizer);
    book->AddPage(chpanel,"Channels");

    wxFont fn(*wxNORMAL_FONT);
    int iSize = fn.GetPixelSize().GetHeight()+6;
    size_t nbands = array.Naxes(2);

    vector<wxColour> colors;
    colors.push_back(wxColour(*wxBLUE));
    colors.push_back(wxColour("FOREST GREEN"));
    colors.push_back(wxColour(*wxRED));
    for(size_t i = colors.size(); i < nbands; i++)
      colors.push_back(wxColour(64*i,64*i+64,64*i+128));

    wxSize is(iSize,iSize);
    wxImageList *icons = new wxImageList(iSize, iSize, true);
    for(size_t i = 0; i < nbands; i++)
      icons->Add(MuniIcon::BulletIcon(wxSize(iSize,iSize),colors[i]));
    chlist->AssignImageList(icons,wxIMAGE_LIST_SMALL);
    
    chlist->InsertColumn(0,wxEmptyString);
    for(size_t i = 0; i < nbands; i++) {
      wxString a;
      a.Printf("Band %d",int(i));
      chlist->InsertItem(i,a,i);
    }
    
    // set item width
    wxSize s = chlist->GetClientSize();
    chlist->SetColumnWidth(0,s.GetWidth());

    Bind(wxEVT_UPDATE_UI,&MuniTune::OnUpdateChannels,this,ID_COLOR_MEAN);
    Bind(wxEVT_UPDATE_UI,&MuniTune::OnUpdateChannels,this,ID_COLOR_WEIGHT);
  }

  // preview
  mini = new MuniMiniDisplay(this,2*config->icon_size,2*config->icon_size);

  // reset
  wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
  topsizer->Add(mini,wxSizerFlags().Border().Center());
  topsizer->Add(book,wxSizerFlags().Border().Expand());

  topsizer->Add(new wxButton(this, ID_RESET, "Reset All"),wxSizerFlags().Center().Border());

  SetSizer(topsizer);

  Bind(wxEVT_CLOSE_WINDOW,&MuniTune::OnClose,this);
  Bind(wxEVT_UPDATE_UI,&MuniTune::OnUpdateNightadj,this,ID_COLOR_NWIDTH);
  Bind(wxEVT_UPDATE_UI,&MuniTune::OnUpdateNightadj,this,ID_COLOR_NTHRESH);
  Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniTune::OnChoiceItt,this,ID_CHOICE_ITT);
  Bind(wxEVT_COMMAND_CHECKBOX_CLICKED,&MuniTune::OnCheckNight,this,ID_CHECK_NIGHT);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniTune::OnReset,this,ID_RESET);
  Bind(wxEVT_UPDATE_UI,&MuniTune::OnUpdateIttpar,this,ID_ITT_AMP);
  Bind(wxEVT_UPDATE_UI,&MuniTune::OnUpdateIttpar,this,ID_ITT_ZERO);
  Bind(EVT_TUNE,&MuniTune::OnTuneFine,this);
  Bind(wxEVT_IDLE,&MuniTune::OnIdle,this);
  Bind(wxEVT_SCROLL_THUMBTRACK,&MuniTune::OnScroll,this);

  FitsImage fi(a);
  fimage = fi.Thumb(2*config->icon_size,2*config->icon_size);
  Render();

  Layout();
  GetSizer()->SetSizeHints(this);

  // save initial settings (used during reseting)
  itt_original = itt;
  color_original = color;
}

void MuniTune::OnClose(wxCloseEvent& event)
{
  delete etune;
  etune = 0;
  wxQueueEvent(GetParent(),event.Clone());
}

void MuniTune::OnIdle(wxIdleEvent& event)
{
  if( etune ) {
    wxQueueEvent(GetParent(),etune->Clone());
    delete etune;
    etune = 0;
  }
}

void MuniTune::Render()
{
  display.SetItt(itt);
  display.SetPalette(pal);
  display.SetColor(color);

  FitsBitmap picture(display.GetImage(fimage));
  wxImage img(picture.GetWidth(),picture.GetHeight(),picture.NewTopsyTurvyRGB());
  static_cast<MuniMiniDisplay*>(mini)->SetImage(img);
}


void MuniTune::OnScrollFinish(wxScrollEvent& event)
{
  OnScroll(event);
}


void MuniTune::OnScroll(wxScrollEvent& event)
{
  wxString entry = event.GetString();
  double x;

  if( event.GetId() == ID_ITT_OFFSET && entry.ToDouble(&x) )
    itt.SetBlack(x);

  if( event.GetId() == ID_ITT_CONTRAST && entry.ToDouble(&x) )
    itt.SetContrast(x);

  if( event.GetId() == ID_ITT_AMP && entry.ToDouble(&x) )
    itt.SetAmp(x);

  if( event.GetId() == ID_ITT_ZERO && entry.ToDouble(&x) )
    itt.SetZero(x);

  if( event.GetId() == ID_COLOR_SATUR && entry.ToDouble(&x) )
    color.SetSaturation(x);

  if( event.GetId() == ID_COLOR_HUE && entry.ToDouble(&x) )
    color.SetHue(x);

  if( event.GetId() == ID_COLOR_WHITEU && entry.ToDouble(&x) )
    color.SetWhitePoint(x,-1.0);

  if( event.GetId() == ID_COLOR_WHITEV && entry.ToDouble(&x) )
    color.SetWhitePoint(-1.0,x);

  if( event.GetId() == ID_COLOR_NTHRESH && entry.ToDouble(&x) )
    color.SetNightThresh(x);

  if( event.GetId() == ID_COLOR_NWIDTH && entry.ToDouble(&x) )
    color.SetNightWidth(x);

  if( event.GetId() == ID_COLOR_MEAN && entry.ToDouble(&x) ) {
    if( chlist->GetSelectedItemCount() == 1) {
      int i = chlist->GetNextItem(-1,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED);
      color.SetLevel(i,x);
    }
  }

  if( event.GetId() == ID_COLOR_WEIGHT && entry.ToDouble(&x) ) {
    if( chlist->GetSelectedItemCount() == 1) {
      int i = chlist->GetNextItem(-1,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED);
      color.SetWeight(i,x);
    }
  }

  Render();
}

MuniTuneAdjuster *MuniTune::CreateIttBlack(wxWindow *tpanel, const FitsItt& itt)
{
  if( itt.GetKind() == ITT_KIND_ABS ) {

    double inc = pow(10.0,log10((itt.GetRangeMax() - itt.GetRangeMin())/10.0));
    MuniTuneAdjuster *b = new MuniTuneAdjuster(tpanel,ID_ITT_OFFSET,itt.GetBlack(),itt.GetRangeMin(),
					     itt.GetRangeMax(),inc,0,"☾","☼");
    b->SetToolTip("Black point is a level value corresponding to black on display.");
    return b;
  }
  else if( itt.GetKind() == ITT_KIND_REL ) {
    
    MuniTuneAdjuster *b = new MuniTuneAdjuster(tpanel,ID_ITT_OFFSET,itt.GetBlack(),-50.0,
					       50.0,1.0,0,"☾","☼");
    b->SetToolTip("Black point is a level value corresponding to black on display.");
    return b;    
  }

  return 0;
}

MuniTuneAdjuster *MuniTune::CreateIttContrast(wxWindow *tpanel, const FitsItt& itt)
{
  if( itt.GetKind() == ITT_KIND_ABS ) {
    float c = itt.GetContrast();
    MuniTuneAdjuster *s = new MuniTuneAdjuster(tpanel,ID_ITT_CONTRAST,c,c/10.0,10.0*c,
					     c/100.0,3,"○","◑");
    s->SetToolTip("Contrast sets a range of levels to display.");
    return s;
  }
  else if( itt.GetKind() == ITT_KIND_REL ) {
    MuniTuneAdjuster *s = new MuniTuneAdjuster(tpanel,ID_ITT_CONTRAST,itt.GetContrast(),0.01,100.0,
					       0.01,3,"○","◑");
    s->SetToolTip("Contrast sets a range of levels to display.");
    return s;
  }
  
  return 0;
}

MuniTuneAdjuster *MuniTune::CreateIttAmount(wxWindow *tpanel, const FitsItt& itt)
{
  adjamount = new MuniTuneAdjuster(tpanel,ID_ITT_AMP,itt.GetAmp(),0.0,2.0,
				   0.1,3,"-","∼");
  adjamount->SetToolTip("Amount sets curvature of tone function.");
  return adjamount;
}

MuniTuneAdjuster *MuniTune::CreateIttZero(wxWindow *tpanel, const FitsItt& itt)
{
  MuniTuneAdjuster *adjzero = new MuniTuneAdjuster(tpanel,ID_ITT_ZERO,itt.GetZero(),-1.0,1.0,
						   0.1,3,"↓","↑");
  adjzero->SetToolTip("Zero sets relative black of tone function.");
  return adjzero;
}

wxNotebookPage *MuniTune::CreateIttTab(wxNotebook *book, const FitsItt& itt)
{
  wxSizerFlags vsizer(1);
  vsizer.Align(wxALIGN_CENTER_VERTICAL).Border(wxLEFT|wxRIGHT).Expand();

  wxNotebookPage *tpanel = new wxPanel(book);
  wxBoxSizer *isizer = new wxBoxSizer(wxVERTICAL);

  wxBoxSizer *typess = new wxBoxSizer(wxHORIZONTAL);
  type_itt = new wxChoice(tpanel,ID_CHOICE_ITT,wxDefaultPosition,
			  wxDefaultSize,FitsItt::Type_str());
  type_itt->SetStringSelection(itt.GetItt_str());

  typess->Add(new wxStaticText(tpanel,wxID_ANY,"Function:"),
	      wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxRIGHT));
  typess->Add(type_itt);

  isizer->Add(typess,wxSizerFlags().Center().Border());

  isizer->Add(new wxStaticLine(tpanel,wxID_ANY),wxSizerFlags().Expand().Border(wxBOTTOM));

  adjblack = CreateIttBlack(tpanel,itt);
  isizer->Add(adjblack,vsizer);

  adjslope = CreateIttContrast(tpanel,itt);
  isizer->Add(adjslope,vsizer);

  wxRadioButton *radio_abs = new wxRadioButton(tpanel,wxID_ANY,"Absolute",wxDefaultPosition,
					       wxDefaultSize,wxRB_GROUP);
  wxRadioButton *radio_rel = new wxRadioButton(tpanel,wxID_ANY,"Relative");
  wxBoxSizer *radios = new wxBoxSizer(wxHORIZONTAL);
  radios->Add(radio_abs);
  radios->Add(radio_rel);
  isizer->Add(radios,wxSizerFlags().Center().Border());

  if( itt.GetKind() == ITT_KIND_ABS )
    radio_abs->SetValue(true);
  else if( itt.GetKind() == ITT_KIND_REL )
    radio_rel->SetValue(true);


  isizer->Add(new wxStaticLine(tpanel,wxID_ANY),wxSizerFlags().Expand().Border());

  adjamount = CreateIttAmount(tpanel,itt);
  isizer->Add(adjamount,vsizer);

  adjzero = CreateIttZero(tpanel,itt);
  isizer->Add(adjzero,vsizer);

  tpanel->SetSizer(isizer);


  Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED,&MuniTune::OnAbsItt,this,radio_abs->GetId());
  Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED,&MuniTune::OnRelItt,this,radio_rel->GetId());

  return tpanel;
}

void MuniTune::OnAbsItt(wxCommandEvent& event)
{
  itt.SetKind(ITT_KIND_ABS);

  wxWindow *tpanel = adjblack->GetParent();
  wxASSERT(tpanel && tpanel == adjslope->GetParent());
  wxSizer *isizer = tpanel->GetSizer();

  MuniTuneAdjuster *b = CreateIttBlack(tpanel,itt);
  isizer->Replace(adjblack,b);
  adjblack->Destroy();
  adjblack = b;

  MuniTuneAdjuster *s = CreateIttContrast(tpanel,itt);
  isizer->Replace(adjslope,s);
  adjslope->Destroy();
  adjslope = s;

  isizer->Layout();
}

void MuniTune::OnRelItt(wxCommandEvent& event)
{
  itt.SetKind(ITT_KIND_REL);

  wxWindow *tpanel = adjblack->GetParent();
  wxASSERT(tpanel && tpanel == adjslope->GetParent());
  wxSizer *isizer = tpanel->GetSizer();

  MuniTuneAdjuster *b = CreateIttBlack(tpanel,itt);
  isizer->Replace(adjblack,b);
  adjblack->Destroy();
  adjblack = b;

  MuniTuneAdjuster *s = CreateIttContrast(tpanel,itt);
  isizer->Replace(adjslope,s);
  adjslope->Destroy();
  adjslope = s;

  isizer->Layout();
}

void MuniTune::OnChoiceItt(wxCommandEvent& event)
{
  MuniTuneEvent ev(EVT_TUNE,ID_ITT_TYPE);
  ev.SetString(event.GetString());
  wxQueueEvent(GetParent(),ev.Clone());

  itt.SetItt(event.GetString());
  Render();
}

void MuniTune::OnChoicePal(wxCommandEvent& event)
{
  MuniTuneEvent ev(EVT_TUNE,ID_PALETTE_TYPE);
  ev.SetString(event.GetString());
  wxQueueEvent(GetParent(),ev.Clone());

  pal.SetPalette(event.GetString());
  lutus->SetPalette(pal);
  Render();
}

void MuniTune::OnCheckNeg(wxCommandEvent& event)
{
  MuniTuneEvent ev(EVT_TUNE,ID_PALETTE_NEGATIVE);
  ev.SetInt(event.IsChecked());
  wxQueueEvent(GetParent(),ev.Clone());

  pal.SetNegative(event.IsChecked());
  lutus->SetPalette(pal);
  Render();
}

void MuniTune::OnCheckNight(wxCommandEvent& event)
{
  config->display_nvision = event.IsChecked();

  MuniTuneEvent ev(EVT_TUNE,ID_COLOR_NVISION);
  ev.SetInt(event.IsChecked());
  wxQueueEvent(GetParent(),ev.Clone());

  color.SetNightVision(event.IsChecked());

  Render();
}

void MuniTune::OnTuneFine(MuniTuneEvent& e)
{
  //  wxLogDebug("MuniTune::OnTuneFine");

  switch(e.GetId()) {
  case ID_ITT_TYPE:   itt.SetItt(e.GetString()); break;
  case ID_ITT_OFFSET: itt.SetBlack(e.x); break;
  case ID_ITT_CONTRAST: itt.SetContrast(e.x); break;
  case ID_ITT_AMP:    itt.SetAmp(e.x); break;
  case ID_ITT_ZERO:   itt.SetZero(e.x); break;

  case ID_COLOR_NVISION: color.SetNightVision(e.GetInt()); break;
  case ID_COLOR_SATUR:   color.SetSaturation(e.x); break;
  case ID_COLOR_HUE:     color.SetHue(e.x); break;
  case ID_COLOR_WHITEU:  color.SetWhitePoint(e.x,-1.0); break;
  case ID_COLOR_WHITEV:  color.SetWhitePoint(-1.0,e.x); break;
  case ID_COLOR_NTHRESH: color.SetNightThresh(e.x); break;
  case ID_COLOR_NWIDTH:  color.SetNightWidth(e.x); break;
  case ID_COLOR_MEAN:    color.SetLevel(e.index,e.x); break;
  case ID_COLOR_WEIGHT:  color.SetWeight(e.index,e.x); break;

  case ID_PALETTE_TYPE:     pal.SetPalette(e.GetString()); break;
  case ID_PALETTE_NEGATIVE: pal.SetNegative(e.GetInt()); break;

  default: wxLogDebug("WARNNING: MuniDisplay::OnTuneFine - unknown ID");
  }

  Render();

  delete etune;
  etune = static_cast<MuniTuneEvent *>(e.Clone());
}



void MuniTune::OnReset(wxCommandEvent& event)
{
  itt = itt_original;
  pal = pal_original;
  pal.SetPalette(pal_original.GetPalette()); // assignent operator ???
  pal.SetNegative(pal_original.GetNegative());
  color = color_original;

  wxWindow *w;

  w = FindWindowById(ID_ITT_OFFSET);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(itt.GetBlack());

  w = FindWindowById(ID_ITT_CONTRAST);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(itt.GetContrast());

  w = FindWindowById(ID_ITT_AMP);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(itt.GetAmp());

  w = FindWindowById(ID_ITT_ZERO);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(itt.GetZero());
  
  w = FindWindowById(ID_CHOICE_PAL);
  if( w )
    static_cast<wxChoice *>(w)->SetStringSelection(pal.GetPalette_str());

  w = FindWindowById(ID_CHECK_NEG);
  if( w )
    static_cast<wxCheckBox *>(w)->SetValue(pal.GetNegative());

  w = FindWindowById(ID_CHOICE_ITT);
  if( w )
    static_cast<wxChoice *>(w)->SetStringSelection(itt.GetItt_str());

  w = FindWindowById(ID_COLOR_SATUR);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(color.GetSaturation());

  w = FindWindowById(ID_COLOR_HUE);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(color.GetHue());

  w = FindWindowById(ID_COLOR_WHITEU);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(color.GetWhitePoint_U());

  w = FindWindowById(ID_COLOR_WHITEV);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(color.GetWhitePoint_V());

  w = FindWindowById(ID_CHECK_NIGHT);
  if( w )
    static_cast<wxCheckBox *>(w)->SetValue(color.GetNightVision());

  w = FindWindowById(ID_COLOR_NTHRESH);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(color.GetNightThresh());

  w = FindWindowById(ID_COLOR_NWIDTH);
  if( w )
    static_cast<MuniTuneAdjuster *>(w)->SetValue(color.GetNightWidth());

  if( lutus )
    lutus->SetPalette(pal);

  Render();
  wxQueueEvent(GetParent(),new MuniTuneEvent(EVT_TUNE,ID_RESET));
}


void MuniTune::OnUpdateIttpar(wxUpdateUIEvent& event)
{
  event.Enable(!itt.IsLinear());
}

void MuniTune::OnUpdateNightadj(wxUpdateUIEvent& event)
{
  event.Enable(config->display_nvision);
}

void MuniTune::OnUpdateChannels(wxUpdateUIEvent& event)
{
  event.Enable(chlist->GetSelectedItemCount() == 1);
}



// --- TuneAdjuster

MuniTuneAdjuster::MuniTuneAdjuster(wxWindow *w, wxWindowID id, double Value,
				   double minValue, double maxValue,
				   double s, unsigned int digits, 
				   const wxString& cmin, const wxString& cmax):
  wxControl(w,id,wxDefaultPosition,wxDefaultSize,wxBORDER_NONE),
  xmin(minValue),xmax(maxValue),x(Value),step(s),imin(0),imax(100)
{
  InitRange();

  slider = new wxSlider(this,wxID_ANY,0,imin,imax,wxDefaultPosition,
			wxDefaultSize,wxSL_HORIZONTAL & ~wxSL_LABELS);

  entry = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,
			       wxDefaultSize,wxSP_ARROW_KEYS,minValue,maxValue,Value,step);
  if( digits > 0 )
    entry->SetDigits(digits);

  wxBoxSizer *topsizer = new wxBoxSizer(wxHORIZONTAL);
  topsizer->Add(new wxStaticText(this,wxID_ANY,cmin),wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
  topsizer->Add(slider,wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Border(wxLEFT|wxRIGHT));
  topsizer->Add(new wxStaticText(this,wxID_ANY,cmax),
		wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxRIGHT));
  topsizer->Add(entry,wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
  SetSizer(topsizer);

  int sid = slider->GetId();
  int eid = entry->GetId();

  Bind(wxEVT_SCROLL_THUMBTRACK,&MuniTuneAdjuster::OnScroll,this,sid);
  Bind(wxEVT_SCROLL_THUMBRELEASE,&MuniTuneAdjuster::OnScrollFinish,this,sid);
  Bind(wxEVT_COMMAND_TEXT_UPDATED,&MuniTuneAdjuster::OnEntry,this,eid);
  Bind(wxEVT_COMMAND_TEXT_ENTER,&MuniTuneAdjuster::OnEntryFinish,this,eid);
  Bind(wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED,&MuniTuneAdjuster::OnSpinDouble,this,eid);

  SetValue(Value);
}

MuniTuneAdjuster::MuniTuneAdjuster(const MuniTuneAdjuster& copy)
{
  wxFAIL_MSG("FitsColorData WE ARE REALY NEED COPY CONSTRUCTOR");
}

MuniTuneAdjuster& MuniTuneAdjuster::operator = (const MuniTuneAdjuster& other)
{
  wxFAIL_MSG("FitsColorData: WE ARE REALLY NEED ASSIGNMENT CONSTRUCTOR");
  return *this;
}


void MuniTuneAdjuster::InitRange()
{
  wxASSERT(xmax > xmin);

  // set ranges
  if( fabs(step) < DBL_EPSILON ) {

    imin = 0;
    imax = 100;

    double foo;

    // estimate of rounded ranges
    double l = log10(xmax - xmin);
    double n = trunc(l);
    double e = modf(l,&foo);
    double g = round(pow(10.0,e)+1.0);
    double r = pow(10.0,n+log10(g));
    
    double xm = (xmax + xmin)/2.0;
    double rr = r/imax*10.0;
    double xr = trunc(xm/rr);
    double xs = rr*xr;
    xmin = xs - r/2.0;
    xmax = xs + r/2.0;
    
    //    wxLogDebug(_("%d %d %f %f"),imin,imax,xmin,xmax);
    //    wxLogDebug(_("%f %f %f %f %f %f %f %f %f"),xmin,xmax,xs,r,l,n,g,xm,xr);
  }

  else {
    
    imin = 0;
    imax = trunc((xmax - xmin)/step);
  }
}

void MuniTuneAdjuster::SetValue(double t)
{
  x = t;
  SetSlider(x);
  entry->SetValue(x);
}

void MuniTuneAdjuster::SetToolTip(const wxString& text)
{
  wxASSERT(entry);
  slider->SetToolTip(text);
  entry->SetToolTip(text+" Use ENTER to apply your changes.");
}

void MuniTuneAdjuster::Scroll(int i)
{
  double a = (xmax - xmin)/double(imax - imin);
  x = a*(i - imin) + xmin;
  
  wxString l;
  l.Printf("%.4g",x);
  entry->SetValue(l);
}

void MuniTuneAdjuster::OnScroll(wxScrollEvent& event)
{
  Scroll(event.GetPosition());

  wxCommandEvent e(wxEVT_SCROLL_THUMBTRACK,GetId());
  wxString a;
  a.Printf("%lf",entry->GetValue());
  e.SetString(a);
  wxQueueEvent(GetParent(),e.Clone());
}

void MuniTuneAdjuster::OnScrollFinish(wxScrollEvent& event)
{
  Scroll(event.GetPosition());
  
  MuniTuneEvent e(EVT_TUNE,GetId());
  e.x = entry->GetValue();
  wxQueueEvent(GetParent(),e.Clone());
}

void MuniTuneAdjuster::SetSlider(double x)
{
  double a = (xmax - xmin)/double(imax - imin);
  i = (x - xmin)/a + imin;
  slider->SetValue(i);
}

void MuniTuneAdjuster::Entry(const wxString& text)
{
  double t;
  if( text.ToDouble(&t) )
    x = t;
}

void MuniTuneAdjuster::OnEntry(wxCommandEvent& event)
{
  Entry(event.GetString());
}

void MuniTuneAdjuster::OnEntryFinish(wxCommandEvent& event)
{
  Entry(event.GetString());

  MuniTuneEvent e(EVT_TUNE,GetId());
  e.x = entry->GetValue();
  wxQueueEvent(GetParent(),e.Clone());
}

void MuniTuneAdjuster::OnSpinDouble(wxSpinDoubleEvent& event)
{
  Entry(event.GetString());
  x = event.GetValue();

  MuniTuneEvent e(EVT_TUNE,GetId());
  e.x = entry->GetValue();
  wxQueueEvent(GetParent(),e.Clone());
}

